package com.panomedic.core;

import com.panomedic.Constants;
import com.panomedic.Options;
import com.panomedic.utils.Utils;
import com.panomedic.colors.ColorConstants;
import com.panomedic.colors.Pixel;
import com.panomedic.core.BoundaryHandler;
import com.panomedic.gui.FileChooser;
import com.panomedic.gui.LUTDialog;
import com.panomedic.gui.WaitDialog;
import com.panomedic.gui.WaitDoer;
import com.panomedic.kernel.Kernel;
import com.panomedic.kernel.VisualHandler;
import com.panomedic.log4j.LogCategories;
import com.panomedic.log4j.Logger;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.prefs.Preferences;
import javax.imageio.ImageIO;
import javax.swing.DefaultListModel;
import javax.swing.JFrame;
import javax.swing.JOptionPane;

/**
 *
 * @author Yare
 */
public class Photos extends DefaultListModel {

    static Logger logger = new Logger();
    private static Photos instance = new Photos();
    private static Preferences prefs = Preferences.userRoot().node(Constants.defNodePath);
    private static Photo result = null;
    private static int csType = ColorConstants.HSV;
    private static Dimension photoSize = null;
    private static double[] minShift = new double[]{0.0, 0.0, 0.0};
    private static double[] maxShift = new double[]{0.0, 0.0, 0.0};
    private static double[] minExpand = new double[]{1.0, 1.0, 1.0};
    private static double[] maxExpand = new double[]{1.0, 1.0, 1.0};
    private static SortedSet<Intersection> intersections = new TreeSet();
    private static HashSet<Intersection> usedInter = new HashSet();
    private static LUT lut;
    private static LUTDialog lutDialog;

    public static LUT getLut() {
        return lut;
    }

    public static void addPhoto(File file) {
        BufferedImage image = null;

        if (file != null) {
            try {
                image = ImageIO.read(file);
            } catch (Exception e) {
                logger.error(LogCategories.IMAGEIO, "Loading image failure.");
            }

            if (photoSize == null) {
                photoSize = new Dimension(image.getWidth(), image.getHeight());
            } else if (image.getWidth() != photoSize.width ||
                    image.getHeight() != photoSize.height) {
                logger.error(LogCategories.PROCESSING, "ERROR: Image is not the same size !!");
                return;
            }
            Photo photo = new Photo(file, image);
            instance.addElement(photo);

            // DEBUG :

//            int[] px = null;
//            int x = 400;
//            int y = 296;
//            px = photo.getRaster().getPixel(x, y, px);
//            Pixel px2 = Pixel.getPixel(prefs.getInt(Constants.CS_TYPE, ColorConstants.HSV), px);
//            double[] array = px2.getComp();
//            logger.info(LogCategories.PROCESSING, "Color: x=" + x + ",y=" + y + "...RGB: " + px[0] + "," + px[1] + "," + px[2] + " ..." + px2.getName() + ": " + array[0] + "," + array[1] + "," + array[2]);
            // ~DEBUG

            logger.info(LogCategories.IMAGEIO, "Loaded image : " + file.getName());
            logger.info(LogCategories.IMAGEIO, "---------------------------------");
        }

    }

    public static Photos getInstance() {
        return instance;
    }

    /**
     * Returns the image result.
     * @return
     */
    public static Photo getResult() {
        if (result == null && Photos.photoSize != null) {
            result = new Photo();
            logger.info(LogCategories.IMAGEIO, "Result made.");
        }
        return result;
    }

    /**
     * Creates intersections from all loaded images. Determines which intersections are most significant and disposes the others. Finally, image processing order is determined.
     */
    public static void makeIns() {
        clearIntersections();
        for (int i = 0; i < instance.getSize(); i++) {
            Photo photo1 = (Photo) instance.get(i);

            for (int j = 0; j < i; j++) {
                if (i != j) {
                    Photo photo2 = (Photo) instance.get(j);
                    if (photo1.hasIntersection(photo2)) {
                        Intersection in = new Intersection(photo1, photo2);
                        if (photo1.hasCommonOpaquePixels(in.getTopLeft(), in.getBottomRight(), photo2)) {
                            intersections.add(in);
                        }
                    }
                }
            }
        }
        logger.info(LogCategories.PREPROCESS, "Determining processing order...");
        usedInter = jarnik();
        floodFill(usedInter);
        logger.info(LogCategories.PREPROCESS, "Order made.");
//        if (usedInter != null) {
//            for (Intersection in : usedInter) {
//                in.determineTransFnc();
//                in.setPhotosShifts2();
//            }
//            countExtremes();
//            normalizeAdjustments();
//            printAdjustments();
//        }
    //checkIntersections();
    //logger.info(LogCategories.PROCESSING, "______ Intersections: " + intersections);
    //logger.info(LogCategories.PROCESSING, "______ MinSpanningTree: " + usedInter);

    }

    /**
     * Histogram stretch version
     */
    public static void processHistCalibration() {

        if (intersections.isEmpty()) {
            JOptionPane.showMessageDialog((JFrame) VisualHandler.getInstance().getFrame(),
                    "There is no intersection.",
                    "Warning",
                    JOptionPane.WARNING_MESSAGE);
            VisualHandler.getInstance().getFrame().setStatus("Failed.");
            return;
        }

        csType = prefs.getInt(Constants.CS_TYPE, ColorConstants.HSV);
        VisualHandler.getInstance().getFrame().setStatus("Making result...");
        VisualHandler.getInstance().getFrame().getToolBar().setShowResultEnabled(false);
        VisualHandler.getInstance().getFrame().getToolBar().setShowLutEnabled(false);

        WaitDoer waitDoer = new WaitDoer();
        new Thread(waitDoer).start();

        logger.debug(LogCategories.PREPROCESS, "___ Processing started ...");
        printProcessParams();


        //Photos.getResult();
        //if (result != null) {
        if (photoSize != null) {
            int order = 1;
            BufferedImage image = Utils.cloneImage((Photos.getPhotoOrder(order)).getImage());
            order++;
            result = new Photo(image);
            for (; order <= Photo.getMaxOrder(); order++) {

//                BufferedImage image = new BufferedImage(Photos.getPhotoSize().width, Photos.getPhotoSize().height,
//                BufferedImage.TYPE_INT_ARGB);
                WritableRaster wraster = image.getRaster();

                //cleaning
                setAllUnshifted();

                Photo p1 = result;
                Photo p2 = getPhotoOrder(order);
                Intersection in = new Intersection(p1, p2);
                in.findBoundary();

                in.determineTransFnc();
//                in.setPhotosShifts2();
                countExtremes();
                normalizeAdjustments();
                printAdjustments2(p1, p2);

//                int maxValue = getMaxValue(100);        // L in CIE-Lab is max 100

                int[] range = ColorConstants.getRange(csType);
                int[] max = ColorConstants.getMax(csType);
                double[] maxHistValues = new double[3];
                for (int j = 0; j < 3; j++) {
                    maxHistValues[j] = Math.max(p1.getShift()[j] + p1.getBLog()[j] * Math.pow(max[j], p1.getCLog()[j]),
                            p2.getShift()[j] + p2.getBLog()[j] * Math.pow(max[j], p2.getCLog()[j]));
//                                                (p2.getShift()[j] + range[j]) * p2.getExpand()[j]);

                }
                //WritableRaster wraster = result.getRaster().createCompatibleWritableRaster();

                BoundaryHandler bh = new BoundaryHandler();
                bh.init(p1, p2);

                for (int y = 0; y < photoSize.height; y++) {

                    bh.clear();
                    for (int x = 0; x < photoSize.width; x++) {
                        if (x == 169 && y == 985) {
                            int breakpoint = 0;
                        }
//                        double[] shift = new double[]{
//                            maxShift[0] - minShift[0],
//                            maxShift[1] - minShift[1],
//                            maxShift[2] - minShift[2]
//                        };

                        double[] shift1 = p1.getShift();//(int) p1.getShift();
                        double[] shift2 = p2.getShift();//(int) p2.getShift();
                        double[] exp1 = p1.getBLog();//getExpand();
                        double[] exp2 = p2.getBLog();//getExpand();
                        double[] pow1 = p1.getCLog();
                        double[] pow2 = p2.getCLog();

                        int rpx[] = new int[]{0, 0, 0, 0};
                        Pixel rpxLAB = Pixel.getPixel(csType);
                        if (in.contains(x, y)) {

                            int px1[] = null;
                            int px2[] = null;
                            px1 = p1.getRaster().getPixel(x, y, px1);
                            px2 = p2.getRaster().getPixel(x, y, px2);
                            Pixel px1Lab = Pixel.getPixel(csType, px1);
                            Pixel px2Lab = Pixel.getPixel(csType, px2);

                            boolean px1o = Utils.isPixelOpaque(px1);
                            boolean px2o = Utils.isPixelOpaque(px2);

                            // inside the intersections
                            if (px1o && px2o) {

                                if (in.inCutBoundary(new Point(x, y))) {
                                    if (y != 0 && !bh.aboveBnd) {
                                        if (in.inCutBoundary(new Point(x, y - 1))) {
                                            bh.aboveBnd = true;
                                        }
                                    }
                                    if (y != photoSize.height && !bh.belowBnd) {
                                        if (in.inCutBoundary(new Point(x, y + 1))) {
                                            bh.belowBnd = true;
                                        }
                                    }
                                    if (x != photoSize.width && (!bh.aboveBnd || !bh.belowBnd) && !in.inCutBoundary(new Point(x + 1, y))) {
                                        bh.change();
                                    }
                                    if (in.isBoundaryEnd(new Point(x, y))) {
                                        bh.change();
                                    }

                                    if (!bh.alreadyChanged) {
                                        bh.change();
                                        bh.alreadyChanged = true;
                                        bh.setLastUsed(result);
                                    } else {
                                    }
                                    px1Lab.powArray(pow1);
                                    px2Lab.powArray(pow2);
                                    px1Lab.mulArray(exp1);
                                    px2Lab.mulArray(exp2);
                                    rpxLAB.mean2Comp(px1Lab, px2Lab, shift1, shift2);

                                } else if (bh.active == p1) {
                                    rpxLAB = px1Lab;
                                    rpxLAB.powArray(pow1);
                                    rpxLAB.mulArray(exp1);
                                    rpxLAB.addArray(shift1);
                                    bh.alreadyChanged = false;
                                    bh.aboveBnd = false;
                                    bh.belowBnd = false;
                                } else if (bh.active == p2) {
                                    rpxLAB = px2Lab;
                                    rpxLAB.powArray(pow2);
                                    rpxLAB.mulArray(exp2);
                                    rpxLAB.addArray(shift2);
                                    bh.alreadyChanged = false;
                                    bh.aboveBnd = false;
                                    bh.belowBnd = false;
                                }
                                rpxLAB.setOpaque(true);

                            } else if (!px1o && !px2o) {
                                rpxLAB.setOpaque(false);

                            } else if (!px1o || !px2o) {
                                rpxLAB.setOpaque(true);
                                if (!px1o) {
                                    rpxLAB = px2Lab;
                                    rpxLAB.powArray(pow2);
                                    rpxLAB.mulArray(exp2);
                                    rpxLAB.addArray(shift2);
                                    bh.active = p2;
                                } else {
                                    rpxLAB = px1Lab;
                                    rpxLAB.powArray(pow1);
                                    rpxLAB.mulArray(exp1);
                                    rpxLAB.addArray(shift1);
                                    ;
                                    bh.active = p1;
                                }
                            }
                        // inside 1 photo && outside the intersection
                        } else {
                            if (p1.contains(x, y)) {
                                rpx = p1.getRaster().getPixel(x, y, rpx);
                                rpxLAB.setComp(rpx);
                                rpxLAB.powArray(pow1);
                                rpxLAB.mulArray(exp1);
                                rpxLAB.addArray(shift1);
                                rpxLAB.setOpaque(true);
                                bh.active = p1;
                            } else if (p2.contains(x, y)) {
                                rpxLAB.setOpaque(true);
                                rpx = p2.getRaster().getPixel(x, y, rpx);
                                rpxLAB.setComp(rpx);
                                rpxLAB.powArray(pow2);
                                rpxLAB.mulArray(exp2);
                                rpxLAB.addArray(shift2);
                                bh.active = p2;
                            } else {
                                rpxLAB.setOpaque(false);
                            }
                        //}
                        }
                        rpxLAB.contract(range, minShift, maxHistValues);
                        rpx = rpxLAB.getRGBA();

                        try {
                            wraster.setPixel(x, y, rpx);
                        } catch (Exception e) {
                            logger.error(LogCategories.PROCESSING, "!!!!! ERROR.....  x:" + x + ", y:" + y + ", rpx: " + Arrays.toString(rpx));
                            Kernel.getInstance().Terminate(1);
                        }
                    } // for x
                    System.out.print(".");
                } // for y


                image.setData(wraster);
//                result.setRaster(wraster);
//                result.setImage(image);
                result = null;
                System.gc();
                result = new Photo(image);

                VisualHandler.getInstance().getFrame().getToolBar().setShowResultEnabled(true);
                waitDoer.stop();
                if (prefs.getInt(Constants.SAVE_END, 0) == 2 ? true : false) {
                    saveResult();
                }

                logger.info(LogCategories.IMAGEIO, "Result photo created.");
                logger.info(LogCategories.IMAGEIO, "---------------------");
                VisualHandler.getInstance().getFrame().setStatus("Result image made (" + order + ").");
            //result.setRaster(wraster);
            //BufferedImage img = new BufferedImage(Photos.getPhotoSize().width,Photos.getPhotoSize().height,
            //        BufferedImage.TYPE_INT_ARGB);
            //img.setData(wraster);
            } // for order

            if (prefs.getInt(Constants.SAVE_END, 0) == 1 ? true : false) {
                saveResult();
            }
        }
    }

    /**
     * LUT Version
     */
    public static void processLevelMapping() {

        if (intersections.isEmpty()) {
            JOptionPane.showMessageDialog((JFrame) VisualHandler.getInstance().getFrame(),
                    "There is no intersection.",
                    "Warning",
                    JOptionPane.WARNING_MESSAGE);
            VisualHandler.getInstance().getFrame().setStatus("Failed.");
            return;
        }

        //  for all photo pairs fused
        csType = prefs.getInt(Constants.CS_TYPE, ColorConstants.HSV);
        VisualHandler.getInstance().getFrame().setStatus("Making result...");
        VisualHandler.getInstance().getFrame().getToolBar().setShowResultEnabled(false);
        VisualHandler.getInstance().getFrame().getToolBar().setShowLutEnabled(false);

        // "Please wait frame"
        WaitDoer waitDoer = new WaitDoer();
        new Thread(waitDoer).start();

        logger.debug(LogCategories.PREPROCESS, "........... Processing started .............");
        printProcessParams();

        //Photos.getResult();
        //if (result != null) {
        if (photoSize != null) {
//            VisualHandler.getInstance().getFrame().initProgressBar((int) ((Photos.getInstance().size() - 1) * photoSize.height));

            int order = 1;
            BufferedImage image = Utils.cloneImage((Photos.getPhotoOrder(order)).getImage());
            order++;
            result = new Photo(image);
            for (; order <= Photo.getMaxOrder(); order++) {

//                BufferedImage image = new BufferedImage(Photos.getPhotoSize().width, Photos.getPhotoSize().height,
//                BufferedImage.TYPE_INT_ARGB);
                WritableRaster wraster = image.getRaster();

                Photo p1 = result;
                Photo p2 = getPhotoOrder(order);
                Intersection in = new Intersection(p1, p2);
                in.findBoundary();

                int[] range = ColorConstants.getRange(csType);

                int lutAccuracy = prefs.getInt(Constants.LUT_ACCURACY, 10);
                if (lutAccuracy < 1) {
                    lutAccuracy = 1;
                }
                lut = new LUT(range, lutAccuracy);
                lut.create(p1, p2, in.topLeft, in.bottomRight);

                BoundaryHandler bh = new BoundaryHandler();
                bh.init(p1, p2);

                logger.debug(LogCategories.PROCESSING, "Row count: ");
                //WritableRaster wraster = result.getRaster().createCompatibleWritableRaster();
                for (int y = 0; y < photoSize.height; y++) {
                    bh.clear();
                    for (int x = 0; x < photoSize.width; x++) {
                        //DEBUG
                        if (x == 169 && y == 985) {
                            int breakpoint = 0;
                        }
                        //~DEBUG
                        int rpx[] = new int[]{0, 0, 0, 0};
                        /** Result pixel in used color space */
                        Pixel rpxLAB = Pixel.getPixel(csType);
                        if (in.contains(x, y)) {

                            int px1[] = null;
                            int px2[] = null;
                            px1 = p1.getRaster().getPixel(x, y, px1);
                            px2 = p2.getRaster().getPixel(x, y, px2);
                            Pixel px1Lab = Pixel.getPixel(csType, px1);
                            Pixel px2Lab = Pixel.getPixel(csType, px2);


                            boolean px1o = Utils.isPixelOpaque(px1);
                            boolean px2o = Utils.isPixelOpaque(px2);

                            // inside the intersections
                            if (px1o && px2o) {
                                if (in.inCutBoundary(new Point(x, y))) {
                                    if (y != 0 && !bh.aboveBnd) {
                                        if (in.inCutBoundary(new Point(x, y - 1))) {
                                            bh.aboveBnd = true;
                                        }
                                    }
                                    if (y != photoSize.height && !bh.belowBnd) {
                                        if (in.inCutBoundary(new Point(x, y + 1))) {
                                            bh.belowBnd = true;
                                        }
                                    }
                                    if (x != photoSize.width && (!bh.aboveBnd || !bh.belowBnd) && !in.inCutBoundary(new Point(x + 1, y))) {
                                        bh.change();
                                    }
                                    if (in.isBoundaryEnd(new Point(x, y))) {
                                        bh.change();
                                    }

                                    if (!bh.alreadyChanged) {
                                        bh.change();
                                        bh.alreadyChanged = true;
                                        bh.setLastUsed(result);
                                    } else {
                                    }
                                    px1Lab.setComp(lut.getMapping(px1Lab.getComp(), p1));
                                    px2Lab.setComp(lut.getMapping(px2Lab.getComp(), p2));
                                    rpxLAB.mean2Comp(px1Lab, px2Lab);

                                } else if (bh.active == p1) {
                                    rpxLAB.setComp(lut.getMapping(px1Lab.getComp(), p1));
                                    bh.alreadyChanged = false;
                                    bh.aboveBnd = false;
                                    bh.belowBnd = false;
                                } else if (bh.active == p2) {
                                    rpxLAB.setComp(lut.getMapping(px2Lab.getComp(), p2));
                                    bh.alreadyChanged = false;
                                    bh.aboveBnd = false;
                                    bh.belowBnd = false;
                                }
                                rpxLAB.setOpaque(true);

                            } else if (!px1o && !px2o) {
                                rpxLAB.setOpaque(false);

                            } else if (!px1o || !px2o) {
                                rpxLAB.setOpaque(true);
                                if (!px1o) {
                                    double[] tmp = px2Lab.getComp();
                                    tmp = lut.getMapping(tmp, p2);
                                    rpxLAB.setComp(tmp);
                                    bh.active = p2;
                                } else {
                                    double[] tmp = px1Lab.getComp();
                                    tmp = lut.getMapping(tmp, p1);
                                    rpxLAB.setComp(tmp);
                                    bh.active = p1;
                                }
                            }

                        // inside 1 photo && outside the intersection
                        } else {
                            // for (int i = 0; i < instance.size(); i++) {
                            // Photo p = (Photo) instance.get(i);

                            if (p1.contains(x, y)) {
                                rpx = p1.getRaster().getPixel(x, y, rpx);
                                rpxLAB.setComp(rpx);
                                double[] tmp = rpxLAB.getComp();
                                tmp = lut.getMapping(tmp, p1);
                                rpxLAB.setComp(tmp);
                                rpxLAB.setOpaque(true);
                                bh.active = p1;
                            }
                            if (p2.contains(x, y)) {
                                rpx = p2.getRaster().getPixel(x, y, rpx);
                                rpxLAB.setComp(rpx);
                                double[] tmp = rpxLAB.getComp();
                                tmp = lut.getMapping(tmp, p2);
                                rpxLAB.setComp(tmp);
                                rpxLAB.setOpaque(true);
                                bh.active = p2;
                            }
                        }
                        rpx = rpxLAB.getRGBA();

                        try {
                            wraster.setPixel(x, y, rpx);
                        } catch (Exception e) {
                            logger.error(LogCategories.PROCESSING, "!!!!! ERROR.....  x:" + x + ", y:" + y + ", rpx: " + Arrays.toString(rpx));
                            Kernel.getInstance().Terminate(1);
                        }
                    } // for x
                    System.out.print(".");
                } // for y


                image.setData(wraster);
//                result.setRaster(wraster);
//                result.setImage(image);
                result = null;
                System.gc();
                result = new Photo(image);

                VisualHandler.getInstance().getFrame().getToolBar().setShowResultEnabled(true);
                VisualHandler.getInstance().getFrame().getToolBar().setShowLutEnabled(true);
                lutDialog = new LUTDialog((Frame) VisualHandler.getInstance().getFrame(), new String("Lookup Table - " + ColorConstants.getName(csType)), lut);

                waitDoer.stop();
                if (prefs.getInt(Constants.SAVE_END, 0) == 2 ? true : false) {
                    saveResult();
                }

                logger.info(LogCategories.PROCESSING, "Finished.");
                logger.info(LogCategories.IMAGEIO, "Result photo #" + (order - 1) + " created.");
                logger.info(LogCategories.IMAGEIO, "---------------------");
                VisualHandler.getInstance().getFrame().setStatus("Result image made (" + order + ").");
            //result.setRaster(wraster);
            //BufferedImage img = new BufferedImage(Photos.getPhotoSize().width,Photos.getPhotoSize().height,
            //        BufferedImage.TYPE_INT_ARGB);
            //img.setData(wraster);
            } // for order

//            VisualHandler.getInstance().getFrame().setProgress(photoSize.height * (Photos.getInstance().size() - 1));
        }
    }

    public static void saveResult() {

        if (result == null) {
            JOptionPane.showMessageDialog((JFrame) VisualHandler.getInstance().getFrame(),
                    "Result image was not created.",
                    "Warning",
                    JOptionPane.WARNING_MESSAGE);
            return;
        }

        FileChooser fc = new FileChooser();
        fc.setMultiSelectionEnabled(false);
        File file = fc.getFile();
        if (file != null) {
            try {
                BufferedImage bi = Photos.getResult().getImage();
                ImageIO.write(bi, "png", file);
                VisualHandler.getInstance().getFrame().setStatus("Result image saved.");
                logger.info(LogCategories.IMAGEIO, "Result image saved.");
            } catch (IOException e) {
                logger.error(LogCategories.IMAGEIO, "Image could not be saved.");
                VisualHandler.getInstance().getFrame().setStatus("Saving failed.");
            }


            // LOGGING
            // TODO Preferences - save logging ?
//            saveLog(file.getPath().substring(0, file.getPath().lastIndexOf(".")) + ".prm");
//            saveLut(file.getPath().substring(0, file.getPath().lastIndexOf(".")));
        }
    }

    public static void printProcessParams() {

        String[] methodStrings = {"Direct Level Mapping", "Histogram Statistics"};
        String methodName = methodStrings[prefs.getInt(Constants.METHOD_CHOICE, Constants.METHOD_STRETCHING)];
        logger.info(LogCategories.PREPROCESS, "Method:\t\t" + methodName);

        int lutAcc = prefs.getInt(Constants.LUT_ACCURACY, Options.LUT_ACCURACY);
        logger.info(LogCategories.PREPROCESS, "LUT Accuracy:\t" + lutAcc);

        int radius = prefs.getInt(Constants.BLUR_RADIUS, 10);
        logger.info(LogCategories.PREPROCESS, "Blur radius:\t" + radius);

        String csTypeName = ColorConstants.SPACE_NAME[prefs.getInt(Constants.CS_TYPE, ColorConstants.HSV)];

        String cs = new String();
        for (int i = 0; i < 3; i++) {
            cs += ColorConstants.getCompName(prefs.getInt(Constants.CS_TYPE, ColorConstants.HSV), i);
            cs += "=";
            cs += Utils.getCompProc(prefs.getInt(Constants.COMP_PROC, 7))[i] ? "1" : "0";
            cs += " ";
        }
        logger.info(LogCategories.PREPROCESS, "Color Space:\t" + csTypeName + " --- " + cs);

    }

    public static void saveLog(String fileName) {
        try {
            PrintStream out = new PrintStream(new FileOutputStream(fileName));

            prefs = Preferences.userRoot().node(Constants.defNodePath);

            String[] methodStrings = {"HistogramStretching", "Lookup Table"};
            String methodName = methodStrings[prefs.getInt(Constants.METHOD_CHOICE, Constants.METHOD_LUT)];
            out.println("Method:\t\t" + methodName);

            int lutAcc = prefs.getInt(Constants.LUT_ACCURACY, Options.LUT_ACCURACY);
            out.println("LUT Accuracy:\t" + lutAcc);

            int radius = prefs.getInt(Constants.BLUR_RADIUS, 10);
            out.println("Blur radius:\t" + radius);

            String csTypeName = ColorConstants.SPACE_NAME[prefs.getInt(Constants.CS_TYPE, ColorConstants.HSV)];
            out.println("Color Space:\t" + csTypeName);
            for (int i = 0; i < 3; i++) {
                out.print(ColorConstants.getCompName(prefs.getInt(Constants.CS_TYPE, ColorConstants.HSV), i));
                out.print(":\t");
                out.print(Utils.getCompProc(prefs.getInt(Constants.COMP_PROC, 7))[i] ? "1" : "0");
                out.println();
            }

            out.close();
        } catch (IOException ex) {
            logger.error(LogCategories.LOGGING, ex);
        }
    }

    private static void saveLut(String substring) {
        if (lutDialog != null) {
            for (int i = 0; i < ColorConstants.NUM_COMP; i++) {
                if (Utils.getCompProc(prefs.getInt(Constants.COMP_PROC, 0))[i]) {

                    int width = lutDialog.getTabbedPane().getWidth();
                    int height = lutDialog.getTabbedPane().getHeight();

                    BufferedImage b = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
                    Graphics2D g = b.createGraphics();
                    lutDialog.getTabbedPane().getComponentAt(i).print(g);
                    try {
                        String compLabel = ColorConstants.getCompName(prefs.getInt(Constants.CS_TYPE, ColorConstants.HSV), i);
                        ImageIO.write(b, "png", new File(substring + "_" + compLabel + ".png"));
                    } catch (Exception e) {
                        logger.error(LogCategories.LOGGING, e.toString());
                    }
                }
            }
        }
    }

    private static void clearResult() {
        result = null;
    }
//    private static int[] contract(int[] px, int divider) {
//        int res[] = new int[px.length];
//        for (int i = 0; i < px.length-1; i++) {
//            res[i] = (int) ((double) (px[i]+(int)minShift) * 255 / divider);
//        }
//        if (px[3] == 255) res[3] = 255;
//        else res[3] = 0;
//        return res;
//        
//    }
//    private static double[] contractLAB(double[] px, double divider) {
//        double res[] = new double[px.length];
//        System.arraycopy(px, 0, res, 0, 3);
//        res[0] = (px[0] + minShift) * 100 / divider;
//        return res;
//        
//    }
    private static int[] addPxInt(int[] px, int addend) {
        int res[] = new int[px.length];
        for (int i = 0; i < px.length - 1; i++) {
            res[i] = px[i] + addend;
        }
        res[3] = 255;
        return res;
    }

    /**
     *  In each photo sets the property ORDER according to order of processing images
     * @param usedInter = set of "edges"
     */
    private static void floodFill(HashSet<Intersection> usedInter) {

        if (usedInter == null) {
            logger.debug(LogCategories.PREPROCESS, "No intersections to floodfill.");
            return;
        }

        Queue<Photo> queue = new LinkedList<Photo>();
        int order = 1;
        queue.add((Photo) Photos.getInstance().get(0));
        while (!queue.isEmpty()) {
            Photo currentPhoto = queue.peek();
            currentPhoto.setOrder(order);
            for (Intersection in : usedInter) {
                if (in.contains(currentPhoto)) {
                    Photo ngb = in.neighbour(currentPhoto);
                    if (ngb.getOrder() == 0) {
                        queue.add(ngb);
                    }
                }
            }
            queue.poll();
            order++;
        }
        Photo.setMaxOrder(--order);
    }

//    private static int getMaxValue(int range) {
//        int max = 0; 
//        for (int i = 0; i < instance.getSize(); i++) {
//                Photo p = (Photo) Photos.getInstance().get(i);
//                int val = (int) ((p.getShift() + range) * p.getExpand());
//                if (max < val) max = val;
//            }
//        logger.info(LogCategories.PROCESSING, "max: "+max);
//        return max;
//    }
    /**
     * counts pixel which has value in the mean of two parameters
     * 
     * @param px1 first pixel
     * @param px2 second pixel
     * @param s1  shift1
     * @param s2  shift2
     * @return mean pixel or null if array are too small ( length <= 2)
     */
    private static void checkIntersections() {

        for (Intersection in : intersections) {
            for (int i = 0; i < instance.getSize(); i++) {
                Photo photo = (Photo) instance.get(i);
                if (in.contains(photo)) {
                    continue;
                }
                photo.surrounds(in);

            }

        }


    }

    /**
     * Finds minimal and maximal shift a and expansion rate for ALL OF THE PHOTOS + result !!
     */
    private static void countExtremes() {
        for (int i = 0; i < minShift.length; i++) {
            minShift[i] = 0.0;
            maxShift[i] = 0.0;
            minExpand[i] = 1.0;
            maxExpand[i] = 1.0;
        }


        for (int i = 0; i < instance.getSize(); i++) {
            Photo p = (Photo) Photos.getInstance().get(i);
            if (p.isShifted()) {
                for (int j = 0; j < minShift.length; j++) {
                    double shift = p.getShift()[j];
                    double expand = p.getExpand()[j];
                    if (shift > maxShift[j]) {
                        maxShift[j] = shift;
                    }
                    if (shift < minShift[j]) {
                        minShift[j] = shift;
                    }
                    if (expand < minExpand[j]) {
                        minExpand[j] = expand;
                    }
                    if (expand > maxExpand[j]) {
                        maxExpand[j] = expand;
                    }
                } // for
            } // if shifted
        } // for Photos
//        Photo p = (Photo) Photos.getResult();
//        if (p.isShifted()) {
//            for (int j = 0; j < minShift.length; j++) {
//                double shift = p.getShift()[j];
//                double expand = p.getExpand()[j];
//                if (shift > maxShift[j]) {
//                    maxShift[j] = shift;
//                }
//                if (shift < minShift[j]) {
//                    minShift[j] = shift;
//                }
//                if (expand < minExpand[j]) {
//                    minExpand[j] = expand;
//                }
//                if (expand > maxExpand[j]) {
//                    maxExpand[j] = expand;
//                }
//            }
//        }

    }

    /** 
     *  Adjusts shifts values in all photos so that minimum of shifts is 0.0
     */
    private static void normalizeAdjustments() {
        for (int j = 0; j < minShift.length; j++) {
            if (minShift[j] < 0) {
                for (int i = 0; i < instance.getSize(); i++) {
                    Photo p = (Photo) Photos.getInstance().get(i);

                    if (p.isShifted()) {
                        double[] shift = p.getShift();
                        shift[j] -= minShift[j];
                        p.setShift(shift);
                    }
                }
            }
            if (minExpand[j] < 1 && minExpand[j] != 0) {
                for (int i = 0; i < instance.getSize(); i++) {
                    Photo p = (Photo) Photos.getInstance().get(i);
                    if (p.isShifted()) {
                        double[] exp = p.getExpand();
                        exp[j] /= minExpand[j];
                        p.setExpand(exp);
                    }
                }
            }
        }


    }

    /** From the intersection data field  (as edges of a graph) makes new graph - minimal spanning tree
     *  (edges sorted by sizes of intersections - in pixels)
     * @return new graph - minimum spanning tree
     */
    private static HashSet<Intersection> jarnik() {

        if (instance.isEmpty() || intersections.isEmpty()) {
            logger.debug(LogCategories.PREPROCESS, "No intersections to arrange.");
//            return null;
        }
        Photo photo = (Photo) instance.get(0);
        photo.setUsedJarnik(true);

        HashSet minSpanTree = new HashSet();

        while (!instance.allUsed()) {

            for (Intersection in : intersections) {
                Photo first = in.getFirst();
                Photo second = in.getSecond();
                if (instance.contains(first) && instance.contains(second)) {
                    if ((first.isUsedJarnik() && !second.isUsedJarnik()) || (!first.isUsedJarnik() && second.isUsedJarnik())) {
                        minSpanTree.add(in);
                        first.setUsedJarnik(true);
                        second.setUsedJarnik(true);
                        break;
                    }
                }
            }
        }
        return minSpanTree;
    }

    /**  Writes values of all shifts of Photos to <i> log </i>.
     * 
     */
    private static void printAdjustments() {

        logger.info(LogCategories.PROCESSING, "--------------------------");
        logger.info(LogCategories.PROCESSING, "[[ Photos adjustments.... ]]");
        for (int i = 0; i < Photos.getInstance().getSize(); i++) {
            Photo photo = (Photo) Photos.getInstance().get(i);
//            logger.info(LogCategories.PROCESSING, i + ": " + photo + " :: " + "shifted: " + photo.isShifted() + " :: " + "shift: " + Arrays.toString(photo.getShift()) + " :: " + "expand: " + Arrays.toString(photo.getExpand()) + " :: " + "order: " + photo.getOrder());
            logger.info(LogCategories.PROCESSING, i + ": " + photo + " :: " + "a: " + Arrays.toString(photo.getShift()) + " :: " + "b: " + Arrays.toString(photo.getBLog()) + " :: " + "b: " + Arrays.toString(photo.getCLog()) + "order: " + photo.getOrder());
        }
    }

    /** To <i> log </i> writes values of all shifts of Photos
     * 
     */
    private static void printAdjustments2(Photo p1, Photo p2) {

        logger.info(LogCategories.PROCESSING, "--------------------------");
        logger.info(LogCategories.PROCESSING, "[[ Photos adjustments.... ]]");

        Photo photo = p1;
//        logger.info(LogCategories.PROCESSING, photo + " :: " + "shifted: " + photo.isShifted() + " :: " + "shift: " + Arrays.toString(photo.getShift()) + " :: " + "expand: " + Arrays.toString(photo.getExpand()) + " :: " + "order: " + photo.getOrder());
        logger.info(LogCategories.PROCESSING, photo + " :: " + "a: " + Arrays.toString(photo.getShift()) + " :: " + "b: " + Arrays.toString(photo.getBLog()) + " :: " + "c: " + Arrays.toString(photo.getCLog()) + " :: order: " + photo.getOrder());
        photo = p2;
//        logger.info(LogCategories.PROCESSING, photo + " :: " + "shifted: " + photo.isShifted() + " :: " + "shift: " + Arrays.toString(photo.getShift()) + " :: " + "expand: " + Arrays.toString(photo.getExpand()) + " :: " + "order: " + photo.getOrder());
       logger.info(LogCategories.PROCESSING, photo + " :: " + "a: " + Arrays.toString(photo.getShift()) + " :: " + "b: " + Arrays.toString(photo.getBLog()) + " :: " + "c: " + Arrays.toString(photo.getCLog()) + " :: order: " + photo.getOrder());
    }
    public static Dimension getPhotoSize() {
        return photoSize;
    }

    @Override
    public void clear() {
        super.clear();
        Photos.clearResult();
        photoSize = null;
        clearIntersections();

        VisualHandler.getInstance().getFrame().getToolBar().setShowLutEnabled(false);
        VisualHandler.getInstance().getFrame().getToolBar().setShowResultEnabled(false);

        System.gc();
    }

    public static void removePhoto(int index) {
        instance.remove(index);
        if (Photos.instance.isEmpty()) {
            Photos.instance.clear();
            return;
        }
        Photos.clearResult();
        makeIns();
    }

    public static void clearIntersections() {
        intersections.clear();
        setAllUnused();
        setAllUnshifted();
        if (usedInter != null) {
            usedInter.clear();
        }
    }

    private boolean allUsed() {
        for (int i = 0; i < instance.getSize(); i++) {
            if (!((Photo) instance.get(i)).isUsedJarnik()) {
                return false;
            }
        }
        return true;
    }

    private static void setAllUnused() {
        for (int i = 0; i < instance.getSize(); i++) {
            ((Photo) instance.get(i)).setUsedJarnik(false);
        }
    }

    private static void setAllUnshifted() {
        for (int i = 0; i < instance.getSize(); i++) {
            Photo p = (Photo) instance.get(i);
            p.setShifted(false);

        }
        if (result != null) {
            result.setShifted(false);
        }
    }

    private static Photo getPhotoOrder(int order) {
        for (int i = 0; i < instance.getSize(); i++) {
            Photo p = (Photo) instance.get(i);
            if (p.getOrder() == order) {
                return p;
            }
        }
        return null;
    }

    private static Photo getIntersectedPhotoOrderLt(int order) {
        Photo curOrderPhoto = Photos.getPhotoOrder(order);
        for (int i = 0; i < instance.getSize(); i++) {
            Photo p = (Photo) instance.get(i);
            if (p.getOrder() < order && p.hasIntersection(curOrderPhoto)) {
                return p;
            }
        }
        return null;
    }//    public static BufferedImage[] getThumbs() {
//        BufferedImage array[] = new BufferedImage[(photos.size())];
//        for ( int i = 0; i < photos.size(); ++i){
//            array[i]=photos.get(i).getThumb();
//            
//        }
//        return array;
//    }
//    
//    public static BufferedImage getFirst() {
//        BufferedImage img = null;
//        if (!photos.isEmpty()) img = photos.get(0).getImage();
//        return img;
//    }
}
